# write by hasaki
# 把整个.py目录全部编译成pyd加密文件
# 注意：如果文件夹同目录没有__init__.py，那么这个文件夹是不会经过加密的
#      pyd不能直接用python去执行，要Import去执行
# 如果编译使用的c++库的位数不一样，那么即是已经编译pyd成功，不同位数的python也是不会成功调用的
# ImportError: DLL load failed: %1 不是有效的 Win32 应用程序
import os
import sys
import subprocess
import platform
import shutil
import json
import codecs


class MakePydAndSo:
    def __init__(self):
        '''把python代码加密'''
        # {'目录':[{'子目录':[],'文件名':'后缀','文件名':'.py'}]}
        self.os_path=os.getcwd()                         # python 启动的文件位置
        self.python_code_file=''                         # 源代码文件地址
        self.current_path=os.getcwd()                    # 遍历到的当前目录
        self.current_platform=platform.platform()        # 当前系统类型
        self.pyd_file_path=''                            # 生成pyd的路径
        self.pyd_file=''                                 # pyd文件

        self.current_file_name=''                        # 当前遍历到文件的名称
        self.is_py_file=False                            # 判断当前文件是否是python文件

    def judge_platform(self):
        '''判断当前的系统是什么'''
        print('system : ',self.current_platform)
    
    def check_py(self):
        '''判断是不是python文件，如果是就返回名称'''
        if self.current_file_name[-3:]=='.py':
            print('this is a python file',self.current_file_name)
            self.log('python文件 : {}\n'.format(self.current_file_name))
            self.is_py_file=True
        else:
            print('this isn py file :',self.current_file_name)
            self.is_py_file=False
        
    def setup_function(self):
        ''' 把file_name 进行pyd化
            win平台下pyd文件生成在setup.py文件的相同目录下，同时生成build文件夹和Lib文件夹
            linux平台下so文件生成在setup.py文件的相同目录下，so文件就在这个目录下
        '''
        if self.is_py_file:
            if 'Window' in self.current_platform[:7]:
                print('file name : {} to pyd file'.format(self.current_file_name))
                temp=self.current_path+'/'+self.current_file_name
                self.log('python - pyd : {} ,{}\n'.format(self.current_path,self.current_file_name))

                json_data={'path':temp}
                # 写入json格式,加密的时候，通过文件名列表加密
                with open(self.os_path+'/hasaki.json','w',encoding='utf-8') as json_file:
                    self.log('写入json文件的内容 : {}\n'.format(temp))
                    json.dump(json_data,json_file,ensure_ascii=False)

                # TODO : 注意！这里是直接写定文件目录，根据需要修改
                subprocess_order='python ./wequant_engine/setup.py build_ext --inplace' # 生成pyd在加密的目录
                subprocess.call(subprocess_order,shell=True)
                print('c file create success ：',self.current_file_name)

                print('begin to build pyd')
                subprocess_order2='python ./wequant_engine/setup.py install --prefix={}'.format(self.os_path)
                subprocess.call(subprocess_order2,shell=True)
                print('pyd build success',temp,'Lib address：',self.os_path)
            else:
                raise Exception("unexpect system platform . Raise error by optimistic")

    def move_pyd(self):
        '''把pyd生成的lib/side-package下的pyd复制到源代码的原目录里
        TODO : __init__.py文件的加密会有延迟生成pyd导致复制pyd文件报错，'''
        print('bring pyd file to the PATH')
        # 截取python文件名不要后缀，为了删除同文件名的.c文件和.py文件
        current_pyd_file=self.find_pyd_full_name()
        
        # 如果那个文件目录没有python加密文件
        if current_pyd_file is None:
            return

        # 拿到文件夹名称，window下采用\\的,也要注意\\ /混合的情况
        tempFileCology=self.current_path.split('\\')[-1]
        if '/' in tempFileCology:
            tempFileCology=tempFileCology.split('/')[-1]
        
        self.pyd_file=self.pyd_file_path+'/'+tempFileCology+'/'+current_pyd_file
        temp=self.current_path+'/'+self.current_file_name+'d'   # 原文件路径加文件名再加上d
        self.log('加密文件的复制 前: {} 后:{} \n'.format(self.pyd_file,temp))
        shutil.copyfile(self.pyd_file,temp)
        print('Move pyd file successed!!',temp)
    
    def delete_py_file(self):
        '''把pyd复制到源代码的原目录下后，删除源代码'''
        print('delete .py file')
        os.remove(self.current_path+'/'+self.current_file_name)
        print('delete origin code success',self.current_file_name)

    def delete_C_file(self):
        '''删除加密过程中产生的c文件'''
        if self.is_py_file:
            print('delete .C file')
            c_file_name=self.current_file_name.replace('.py','.c')
            os.remove(self.current_path+'/'+c_file_name)
            print('delete .c file success')
    
    def renameAll(self):
        '''如果是使用inplace加密，加密文件就直接在源代码目录了，但是源代码文件的名中是包含着解析器的信息的如:cp36-win_amd64要删掉'''
        for i,_,k in os.walk(self.python_code_file):
            # 只有目录没有文件得情况
            if k==[]:
                continue
            for k_i in k:
                if 'cp36-win_amd64.' in k_i:
                    originFileName=i+'/'+k_i
                    afterFileName=originFileName.replace('cp36-win_amd64.','')
                    os.rename(originFileName,afterFileName)
                    # init的pyd情况
                    if '__init__' in k_i:
                        # 最后再删除init.py
                        os.remove(i+'/'+'__init__.py')
                        self.log('rename函数删除 : {} \n'.format(i+'/'+'__init__.py'))
    
    # ------------------------------------------------------------------------------------------
    # 辅助主流程的功能
    def find_pyd_full_name(self):
        '''找到加密文件的全名
            如：main.cp36-win_amd64.pyd
        '''
        for i,_,k in os.walk(self.pyd_file_path):
            self.log('获取加密文件夹中的文件名: {} 文件夹名: {}\n'.format(k,i))
            for k_i in k:
                if k_i.split('.')[0]==self.current_file_name.replace('.py',''):
                    #self.log('获取加密文件夹中的文件名: {}\n'.format(k_i))
                    return k_i
    
    def log(self,msg):
        '''加密有太多打印，所以把自身的打印另保存
        '''
        with open(os.getcwd()+'/pydLog.txt','a') as f:
            f.write(msg)

    # ------------------------------------------------------------------------------------------
    def setting(self,origin_code_path):
        '''一些启动设置'''
        self.judge_platform()
        self.python_code_file=origin_code_path
        
        if 'Window' in self.current_platform:
            self.pyd_file_path=self.os_path+'/Lib/site-packages/'
        
        else:
            raise Exception('wrong SYSTEM,not window system')

    def run(self):
        '''开始遍历源代码'''
        for i,_,k in os.walk(self.python_code_file):
            # 只有目录没有文件得情况
            if k==[]:
                continue
            
            # 有目录也有文件的情况,先遍历该目录下所有的文件，k_i就是文件名称
            self.current_path=i
            for k_i in k:
                if 'setup.py' == k_i:
                    continue
                self.current_file_name=k_i
                self.check_py()

                if not self.is_py_file:
                    continue
                
                self.setup_function()
                #self.move_pyd()  这个会报错
                self.delete_C_file()
                if '__init__.py' in k_i:
                    continue
                self.delete_py_file()
                self.log('\n')
        
        # 最后清理目录
        self.renameAll()
                

a=MakePydAndSo()
python_code=os.getcwd()+'/wequant_engine'     # 需要加密的python 源代码文件夹，如果不是这个文件夹，搜索这个代码中的 wequant_engine统一即可
a.setting(python_code)
a.run()
#shutil.copy(python_code,'C:/Users/win7/Desktop')